﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using SharpChannel.Core.Coder;
using SharpChannel.Core.Packet;
using SharpChannel.Util;

namespace SharpChannel.Core
{
    public abstract class BaseSession : IUnique
    {

        /// <summary>
        /// 接收缓冲区（临时用）
        /// </summary>
        private readonly byte[] receiveBuffer;

        /// <summary>
        /// socket
        /// </summary>
        private readonly Socket socket;

        /// <summary>
        /// 接收环形队列（缓冲区的数据最终都要放到队列中）
        /// </summary>
        private readonly IBufferQueue<byte> bufferQueue;

        /// <summary>
        /// 协议编码解码器
        /// </summary>
        private readonly ICoder coder;

        /// <summary>
        /// session 是否已经失效
        /// </summary>
        public int Deleted { get; set; }

        /// <summary>
        /// 全局session序列号
        /// </summary>
        protected static int GlobalSequence = ConfigurationUtil.GetSessionInitGlobalSequenceId();

        /// <summary>
        /// constructor.
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="reciveBufferSize"></param>
        protected BaseSession(Socket socket, int reciveBufferSize = 0)
        {
            reciveBufferSize = reciveBufferSize <= 0 ? ConfigurationUtil.GetReceiveBufferSize() : reciveBufferSize;

            receiveBuffer = new byte[reciveBufferSize];
            this.socket = socket;
            Id = Interlocked.Increment(ref GlobalSequence);
            bufferQueue = BufferQueueManager.GetInstance().GetByteBufferQueue();
            coder = SharpCoderManager.GetInstance().GetCoder();
            bufferQueue.Id = Id;
            Deleted = 0;
        }

        /// <summary>
        /// send message.
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public int Send(byte[] buffer, int offset = 0, int length = -1)
        {
            if (length < 0)
                length = buffer.Length;
            buffer = coder.Encode(buffer, offset, length);
            length = buffer.Length;
            int sent = 0;
            while (sent < length) sent += socket.Send(buffer, offset + sent, length - sent, SocketFlags.None);
            return sent;
        }

        /// <summary>
        /// recieved message.
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="length"></param>
        public void Receive(byte[] buffer, int offset = 0, int length = -1)
        {
            bufferQueue.EnQueue(buffer, offset, length);
        }

        /// <summary>
        /// current session has complete message.
        /// </summary>
        /// <returns></returns>
        public bool CanDecode() { return coder.CanDecode(bufferQueue); }

        /// <summary>
        /// decode message.
        /// </summary>
        /// <returns></returns>
        public byte[] Decode() { return coder.Decode(bufferQueue); }

        /// <summary>
        /// try to rerieve message until millseconds.
        /// </summary>
        /// <param name="millseconds"></param>
        /// <returns></returns>
        public byte[] ReceiveUntilDecode(int millseconds)
        {
            var timeOut = false;
            var curentTime = DateTime.Now.Ticks;
            while (!CanDecode())
            {
                if (socket.Poll(1000000, SelectMode.SelectRead))
                {
                    var len = socket.Receive(receiveBuffer);
                    if (len > 0)
                    {
                        Receive(receiveBuffer, 0, len);
                    }
                }
                else
                {
                    Thread.Sleep(100);
                }
                if ((DateTime.Now.Ticks - curentTime) / 10000 <= millseconds) continue;
                timeOut = true;
                break;
            }
            if (timeOut)
            {
                LoggerHelper.GetLogger().Error("timeout occured for request:");
                throw new Exceptions.ReceiveTimeoutException("receive time out:" + socket);
            }
            return Decode();
        }


        public IPEndPoint EndPoint { get; set; }

        public Socket GetSocket() { return socket; }

        public object SessionData { get; set; }


        #region IUnique implementation

        public long Id
        {
            get;
            set;
        }

        #endregion

        public void AsyncSend(byte[] buffer, AsyncCallback callBack = null, int offset = 0, int length = -1)
        {
            throw new NotImplementedException();
        }

    }
}

